﻿using MongoDB.Driver;
using Sustainalytics.BusinessResearchProductStates.Entities.ResearchState;
using Sustainalytics.ClientDW.DataAccessLayer;
using Sustainalytics.ClientDW.Entities;
using Sustainalytics.GCCS.DataAccessLayer;
using Sustainalytics.MongoDB.Extensions;
using Sustainalytics.Synchronization.Interface;
using Sustainalytics.UrlHelpers;
using Sustainalytics.Utils;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;

namespace Sustainalytics.ClientDW.Synchronization
{
    public class GlobalCompactSyncStep : ISyncStep
    {
        private static IClientDWGlobalCompactRepository _clientDwGlobalCompactRepository;
        private static IGCCSDataReader _gccsDataReader;

        public SyncStepOrder ExecutionOrder => SyncStepOrder.SyncGlobalCompact;

        public static ISyncStep GetInstance(IClientDWGlobalCompactRepository clientDwGlobalCompactRepository, IGCCSDataReader gccsDataReader)
        {
            if (clientDwGlobalCompactRepository == null)
                throw new ArgumentException("An instance of type IClientDWGlobalCompactRepository must be provided in order to create an GlobalCompactSync instance!");

            if (gccsDataReader == null)
                throw new ArgumentException("An instance of type IGCCSDataReader must be provided in order to create an GlobalCompactSync instance!");

            _clientDwGlobalCompactRepository = clientDwGlobalCompactRepository;
            _gccsDataReader = gccsDataReader;

            return new GlobalCompactSyncStep();
        }

        public SyncContext Start(SyncContext syncContext)
        {
            var currentChanges = new List<ChangedElement>();

            // get URL friendly generator
            var urlFriendlyGenerator = syncContext.PreviousChanges.GetUrlFriendlyGenerator("CompanyName");

            if (urlFriendlyGenerator == null)
            {
                urlFriendlyGenerator = new UrlFriendlyGenerator();
                currentChanges.AddUrlFriendlyGenerator("CompanyName", urlFriendlyGenerator);
            }

            CollectRepositories(syncContext);

            if (SyncQuarter(ref syncContext))
            {
                return syncContext;
            }

            EnsureCompanyUniverses(syncContext);

            var cccsResearchCompanyProfiles = GetGccsResearchCompanyProfiles(syncContext);

            var overallCompanySummaryDocuments = SyncOverallCompanySummary(syncContext, cccsResearchCompanyProfiles, urlFriendlyGenerator);

            var gccsResearchCompanyIds = cccsResearchCompanyProfiles.Select(cp => cp.Key).ToList();

            var principlesSummaryExtended = SyncPrincipleSummaryExtendeds(syncContext, gccsResearchCompanyIds);

            SyncOverallSummaryExtended(syncContext, overallCompanySummaryDocuments, principlesSummaryExtended, urlFriendlyGenerator);

            SyncPrincipleSummary(syncContext, principlesSummaryExtended);

            SyncPrincipleEventIndicators(syncContext, gccsResearchCompanyIds, principlesSummaryExtended);

            SyncSources(syncContext, gccsResearchCompanyIds);

            currentChanges.Add(new ChangedElement { AffectedKey = "Global Compact Documents Updated" });
            syncContext.AddManyChanges(currentChanges);

            syncContext.Status = SyncOperationStatus.Succeeded;

            return syncContext;
        }

        private static void CollectRepositories(SyncContext syncContext)
        {
            if (!syncContext.GAConnectionStrings.Any())
                throw new ArgumentException("GAConnectionStrings is not provided!");

            if (ConfigurationManager.AppSettings["CommandTimeout"] == null)
                throw new Exception("CommandTimeout key is missing!");

            var commandTimeout = SyncHelper.ReadCommandTimeOutFromConfig();
            syncContext.Log($"DataReader CommandTimeout: {commandTimeout}");

            string clientDwConnectionString;

            if (!syncContext.GAConnectionStrings.TryGetValue("ClientDW", out clientDwConnectionString))
            {
                throw new ArgumentException("No connection string found for ClientDW!");
            }
            _clientDwGlobalCompactRepository = ClientDWGlobalCompactRepository.GetInstance(clientDwConnectionString);
            syncContext.Log("IClientDWGlobalCompactRepository acquired based on connection string: " + clientDwConnectionString);

            string gccsConnectionString;

            if (!syncContext.GAConnectionStrings.TryGetValue("GCCSConnectionString", out gccsConnectionString))
            {
                throw new ArgumentException("No connection string found for GCCS!");
            }

            _gccsDataReader = GCCSDataReader.GetInstance(gccsConnectionString, commandTimeout);
            syncContext.Log("IGCCSDataReader acquired based on connection string: " + gccsConnectionString);
        }

        private static void EnsureCompanyUniverses(SyncContext syncContext)
        {
            if (syncContext.CompanyUniverses != null) return;

            var universesRepository = UniversesRepository.GetUniversesRepository(syncContext.GAConnectionStrings["ClientUniverses"]);
            syncContext.CompanyUniverses = universesRepository.GetAllUniverseCompany();
        }

        private static bool SyncQuarter(ref SyncContext syncContext)
        {
            var currentQuarter = _gccsDataReader.GetCurrentQuarter();

            if (currentQuarter == null) return false;

            syncContext.Log(
                $"Current Quarter: {currentQuarter.Date}, IsClosed: {currentQuarter.IsClosed}, IsPublished: {currentQuarter.IsPublished}, PublishedDateTime: {currentQuarter.PublishedDateTime}");

            if (currentQuarter.IsPublished == false)
            {
                syncContext.Log("No data to process due current quarter is published == false ");
                return true;
            }

            var syncGc = true;

            var lastCurrentQuarter = _clientDwGlobalCompactRepository.GetCurrentQuarter();

            if (lastCurrentQuarter != null)
                syncGc = lastCurrentQuarter.PublishedDateTime != currentQuarter.PublishedDateTime;

            if (!syncGc)
            {
                syncContext.Log("No data to process due mongodb quarter published datetime == sql quarter published datetime");
                return true;
            }
            else
            {
                syncContext.PreviousChanges = syncContext.PreviousChanges.Append(new ChangedElement { AffectedKey = "Global Compact Documents Updated" });
            }

            syncContext.PreviousChanges = syncContext.PreviousChanges.Append(new ChangedElement { AffectedKey = "Global Compact Documents Updated" });
            _clientDwGlobalCompactRepository.SaveCurrentQuarter(currentQuarter);

            return false;
        }

        private static Dictionary<Guid, GCCompanyProfile> GetGccsResearchCompanyProfiles(SyncContext syncContext)
        {
            var builder = Builders<CompanyProfileResearchState>.Filter;
            var filter = builder.Eq(cp => cp.ResearchParentId, null)
                         & builder.Eq(cp => cp.GccsResearchState.ResearchState, ResearchState.ResearchAvailable);

            var companyIds =
                MongoFactory.GetCollectionFromDatabase<CompanyProfileResearchState>(
                    syncContext.GAConnectionStrings["ClientDW"])
                    .Find(filter)
                    .Project(cd => cd.Id)
                    .ToList();

            syncContext.Log(companyIds.Count + " companies retrieved with GCCS research available.");

            var corporateData =
                MongoFactory.GetCollectionFromDatabase<CorporateData>(syncContext.GAConnectionStrings["ClientDW"])
                    .Find(cd => companyIds.Contains(cd.Id))
                    .Project(cd => new GCCompanyProfile
                    {
                        Id = cd.Id,
                        Name = cd.CompanyName,
                        NameLowercase = cd.CompanyName.ToLowerInvariant(),
                        BusinessDescription = cd.BusinessDescription,
                        Country = cd.Country,
                        Industry = cd.PeerGroup,
                        MainTicker = cd.MainTicker,
                        MainExchange = cd.MainExchange
                    })
                    .ToList();

            syncContext.Log(companyIds.Count + " corporate data retrieved.");

            var dictionary = corporateData.ToDictionary(g => g.Id);

            return dictionary;
        }

        private static List<GCOverallCompanySummary> SyncOverallCompanySummary(SyncContext syncContext, IReadOnlyDictionary<Guid, GCCompanyProfile> companyProfiles, UrlFriendlyGenerator urlFriendlyGenerator)
        {
            var overallCompanySummaryDocuments = new List<GCOverallCompanySummary>();

            var overallCompanySummary = _gccsDataReader.GetOverallCompaniesSummary();

            syncContext.Log(overallCompanySummary.Count + " GCOverallCompanySummary entities retrieved.");

            // Add distinct compliant companies based on CDDB research state active
            // (that means companies complaint, non-complaint and watchlist)
            // to existing ones non compliant companies loaded from GCCS DataWarehouse.

            var gccsResearchCompanyIds = companyProfiles.Select(cp => cp.Key);

            var loadedCompanyIds = overallCompanySummary
                .Where(ocs => gccsResearchCompanyIds.Contains(ocs.Id))
                .Select(ocs => ocs.Id)
                .ToList();

            var compliantCompanySummaryDocuments = gccsResearchCompanyIds
                .Except(loadedCompanyIds)
                .Select(compannyId => new GCOverallCompanySummary()
                {
                    Id = compannyId,
                    LastUpdated = DateTime.UtcNow
                }).ToList();

            overallCompanySummary.AddRange(compliantCompanySummaryDocuments);

            syncContext.Log(compliantCompanySummaryDocuments.Count + " compliant companies added from CDDB.");

            overallCompanySummary.ForEach(ocs =>
            {
                ocs.OverallCompanyAssessmentEffectiveSince = DateTime.UtcNow;

                ocs.CompanyProfile = companyProfiles.ContainsKey(ocs.Id)
                    ? companyProfiles[ocs.Id]
                    : null;

                if (ocs.CompanyProfile == null)
                {
                    syncContext.Log($"GCOverallCompanySummary with id:{ocs.Id} has no active research in CDDB!");
                }
                else
                {
                    // set company URL friendly name
                    ocs.CompanyProfile.NameUrlFriendly =
                        urlFriendlyGenerator.GetFriendlyNameById(ocs.CompanyProfile.Id, ocs.CompanyProfile.Name);

                    ocs.UniverseIds = syncContext.CompanyUniverses.ContainsKey(ocs.Id)
                        ? syncContext.CompanyUniverses[ocs.Id]
                        : null;

                    overallCompanySummaryDocuments.Add(ocs);
                }
            });

            _clientDwGlobalCompactRepository.SaveGcOverallCompanySummary(overallCompanySummaryDocuments);

            syncContext.Log(overallCompanySummaryDocuments.Count + " GCOverallCompanySummary entities saved");

            return overallCompanySummaryDocuments;
        }

        private static List<GCPrincipleSummaryExtended> SyncPrincipleSummaryExtendeds(SyncContext syncContext, List<Guid> companyIds)
        {
            var principlesSummary = _gccsDataReader.GetPrinciplesSummaryExtended();
            syncContext.Log(principlesSummary.Count + " GCPrincipleSummaryExtended entities retrieved.");

            var principlesSummaryDocuments = principlesSummary.Where(ps => companyIds.Contains(ps.CompanyId)).ToList();

            _clientDwGlobalCompactRepository.SaveGCPrincipleSummaryExtended(principlesSummaryDocuments);

            syncContext.Log(principlesSummaryDocuments.Count + " GCPrincipleSummaryExtended entities saved");

            return principlesSummaryDocuments;
        }

        private static void SyncOverallSummaryExtended(SyncContext syncContext, List<GCOverallCompanySummary> overallCompanySummaryDocuments,
            List<GCPrincipleSummaryExtended> principlesSummary, UrlFriendlyGenerator urlFriendlyGenerator)
        {
            var gcOverallCompanySummaryExtendedDocuments = new List<GCOverallCompanySummaryExtended>();

            overallCompanySummaryDocuments.ForEach(ocs =>
            {
                var principles = principlesSummary.Where(p => p.CompanyId == ocs.Id).ToList();

                var gcOverallCompanySummaryExtendedDocument = new GCOverallCompanySummaryExtended
                {
                    Id = ocs.Id,
                    CompanyProfile = new GCCompanyProfile
                    {
                        Id = ocs.CompanyProfile.Id,
                        Name = ocs.CompanyProfile.Name,
                        NameLowercase = ocs.CompanyProfile.NameLowercase,
                        Country = ocs.CompanyProfile.Country,
                        Industry = ocs.CompanyProfile.Industry,
                        MainIsin = ocs.CompanyProfile.MainIsin,
                        MainSedol = ocs.CompanyProfile.MainSedol,
                        MainExchange = ocs.CompanyProfile.MainExchange,
                        MainTicker = ocs.CompanyProfile.MainTicker,
                        BusinessDescription = ocs.CompanyProfile.BusinessDescription
                    },
                    GCSignatorySince = ocs.GCSignatorySince,
                    OverallCompanyAssessment = ocs.OverallCompanyAssessment,
                    OverallCompanyAssessmentEffectiveSince = ocs.OverallCompanyAssessmentEffectiveSince,
                    ReasonForNonCompliance = ocs.ReasonForNonCompliance,
                    LastUpdated = ocs.LastUpdated,
                    NonCompliantPrinciples = principles
                        .Where(p => p.PrincipleAssessment == GCAssessment.NonCompliant)
                        .Select(p => p.Principle).OrderBy(p => p.Id).ToList(),
                    NonCompliantSince = principles
                        .Where(p => p.PrincipleAssessment == GCAssessment.NonCompliant)
                        .OrderBy(p => p.PrincipleAssessmentEffectiveSince)
                        .Select(p => p.PrincipleAssessmentEffectiveSince).FirstOrDefault(),
                    WatchListPrinciples = principles
                        .Where(p => p.PrincipleAssessment == GCAssessment.Watchlist)
                        .Select(p => p.Principle).OrderBy(p => p.Id).ToList(),
                    WatchListSince = principles
                        .Where(p => p.PrincipleAssessment == GCAssessment.Watchlist)
                        .OrderBy(p => p.PrincipleAssessmentEffectiveSince)
                        .Select(p => p.PrincipleAssessmentEffectiveSince).FirstOrDefault()
                };

                // set company friendly name
                gcOverallCompanySummaryExtendedDocument.CompanyProfile.NameUrlFriendly =
                    urlFriendlyGenerator.GetFriendlyNameById(gcOverallCompanySummaryExtendedDocument.CompanyProfile.Id, gcOverallCompanySummaryExtendedDocument.CompanyProfile.Name);

                gcOverallCompanySummaryExtendedDocument.UniverseIds =
                    syncContext.CompanyUniverses.ContainsKey(ocs.Id)
                        ? syncContext.CompanyUniverses[ocs.Id]
                        : null;

                gcOverallCompanySummaryExtendedDocuments.Add(gcOverallCompanySummaryExtendedDocument);
            });

            _clientDwGlobalCompactRepository.SaveGcOverallCompanyExtended(gcOverallCompanySummaryExtendedDocuments);

            syncContext.Log(gcOverallCompanySummaryExtendedDocuments.Count +
                                   " GCOverallCompanySummaryExtended entities saved");
        }

        private static void SyncPrincipleSummary(SyncContext syncContext, List<GCPrincipleSummaryExtended> principlesSummaryExtended)
        {
            var gcPrincipleSummaryDocuments = new List<GCPrincipleSummary>();

            var gcPrincipleConclusionDocuments = new List<GCPrincipleConclusion>();

            principlesSummaryExtended.ForEach(ps =>
            {
                gcPrincipleSummaryDocuments.Add(new GCPrincipleSummary
                {
                    Id = ps.Id,
                    CompanyId = ps.CompanyId,
                    Issue = ps.Issue,
                    Principle = new GCPrincipleTemplate
                    {
                        Id = ps.Principle.Id,
                        Name = ps.Principle.Name,
                        Description = ps.Principle.Description,
                        Pillar = new GCPillarTemplate
                        {
                            Id = ps.Principle.Pillar.Id,
                            Name = ps.Principle.Pillar.Name
                        }
                    },
                    PrincipleAssessment = ps.PrincipleAssessment,
                    PrincipleAssessmentEffectiveSince = ps.PrincipleAssessmentEffectiveSince
                });

                var gcPrincipleConclusionDocument = new GCPrincipleConclusion
                {
                    Id = ps.Id,
                    AssessmentSummaryAndConclusionIntroductionSentence =
                        ps.AssessmentSummaryAndConclusionIntroductionSentence,
                    AssessmentComments = ps.AssessmentComments
                };

                gcPrincipleConclusionDocuments.Add(gcPrincipleConclusionDocument);
            });

            _clientDwGlobalCompactRepository.SaveGCPrincipleSummary(gcPrincipleSummaryDocuments);
            syncContext.Log(gcPrincipleSummaryDocuments.Count + " GCPrincipleSummary entities saved");

            _clientDwGlobalCompactRepository.SaveGcPrincipleConclusion(gcPrincipleConclusionDocuments);
            syncContext.Log(gcPrincipleConclusionDocuments.Count + " GCPrincipleConclusion entities saved");
        }

        private static void SyncPrincipleEventIndicators(SyncContext syncContext, List<Guid> companyIds, List<GCPrincipleSummaryExtended> principlesSummary)
        {
            var principleEventIndicators = _gccsDataReader.GetPrincipleEventIndicators();
            syncContext.Log(principleEventIndicators.Count + " GCPrincipleEventIndicatorExtended entities retrieved.");

            var principleEventIndicatorsDocuments = principleEventIndicators.Where(ps => companyIds.Contains(ps.CompanyId)).ToList();

            _clientDwGlobalCompactRepository.SaveGCPrincipleEventIndicatorExtended(principleEventIndicatorsDocuments);

            syncContext.Log(principleEventIndicatorsDocuments.Count + " GCPrincipleEventIndicatorExtended entities saved");

            var gcPrincipleEventIndicatorDocuments = new List<GCPrincipleEventIndicator>();

            var gcPrincipleEventIndicatorImpactDocuments = new List<GCPrincipleEventIndicatorImpact>();

            var gcprincipleEventIndicatorManagementDocuments = new List<GCPrincipleEventIndicatorManagement>();

            principleEventIndicatorsDocuments.ForEach(pei =>
            {
                gcPrincipleEventIndicatorDocuments.Add(new GCPrincipleEventIndicator
                {
                    Id = pei.Id,
                    PrincipleSummaryId =
                        principlesSummary.Where(
                            p => p.CompanyId == pei.CompanyId && p.Principle.Id == pei.PrincipleTemplateId)
                            .Select(p => p.Id)
                            .SingleOrDefault(),
                    IssueType = pei.IssueType,
                    EventIndicatorAssessment = pei.EventIndicatorAssessment,
                    EventSummary = pei.EventSummary
                });

                var gcPrincipleEventIndicatorImpactDocument = new GCPrincipleEventIndicatorImpact
                {
                    Id = pei.Id,
                    PrincipleSummaryId =
                        principlesSummary.Where(
                            p => p.CompanyId == pei.CompanyId && p.Principle.Id == pei.PrincipleTemplateId)
                            .Select(p => p.Id)
                            .SingleOrDefault(),
                    IssueType = pei.IssueType,
                    EventIndicatorAssessment = pei.EventIndicatorAssessment,
                    EventSummary = pei.EventSummary,
                    ImpactSummary = pei.ImpactSummary,
                    ImpactComments = pei.ImpactComments
                };

                gcPrincipleEventIndicatorImpactDocuments.Add(gcPrincipleEventIndicatorImpactDocument);

                var gcprincipleEventIndicatorManagementDocument = new GCPrincipleEventIndicatorManagement
                {
                    Id = pei.Id,
                    PrincipleSummaryId =
                        principlesSummary.Where(
                            p => p.CompanyId == pei.CompanyId && p.Principle.Id == pei.PrincipleTemplateId)
                            .Select(p => p.Id)
                            .SingleOrDefault(),
                    IssueType = pei.IssueType,
                    EventIndicatorAssessment = pei.EventIndicatorAssessment,
                    EventSummary = pei.EventSummary,
                    ManagementSummary = pei.ManagementSummary,
                    ManagementComments = pei.ManagementComments
                };

                gcprincipleEventIndicatorManagementDocuments.Add(gcprincipleEventIndicatorManagementDocument);
            });

            _clientDwGlobalCompactRepository.SaveGCPrincipleEventIndicator(gcPrincipleEventIndicatorDocuments);
            syncContext.Log(gcPrincipleEventIndicatorDocuments.Count + " GCPrincipleEventIndicator entities saved");

            _clientDwGlobalCompactRepository.SaveGCPrincipleEventIndicatorImpact(gcPrincipleEventIndicatorImpactDocuments);
            syncContext.Log(gcPrincipleEventIndicatorImpactDocuments.Count + " GCPrincipleEventIndicatorImpact entities saved");

            _clientDwGlobalCompactRepository.SaveGCPrincipleEventIndicatorManagement(gcprincipleEventIndicatorManagementDocuments);
            syncContext.Log(gcprincipleEventIndicatorManagementDocuments.Count + " GCPrincipleEventIndicatorManagement entities saved");
        }

        private static void SyncSources(SyncContext syncContext, List<Guid> companyIds)
        {
            var sources = _gccsDataReader.GetSources();
            syncContext.Log(sources.Count + " GCSource entities retrieved.");

            var sourcesDocuments = sources.Where(ps => companyIds.Contains(ps.CompanyId)).ToList();

            _clientDwGlobalCompactRepository.SaveGCSources(sourcesDocuments);
            syncContext.Log(sourcesDocuments.Count + " GCSource entities saved");
        }
    }
}
